<!-- 树形下拉选择组件 license by http://eleadmin.com -->
<template>
  <ele-basic-select
    ref="select"
    :value="value"
    :multiple="multiple"
    :disabled="disabled || loading"
    :size="size"
    :clearable="clearable"
    :collapse-tags="collapseTags"
    :placeholder="placeholder"
    :automatic-dropdown="automaticDropdown"
    :popper-class="['ele-tree-select-dropdown', popperClass].join(' ')"
    :popper-options="popperOptions"
    :id="id"
    :name="name"
    :autocomplete="autocomplete"
    :selected-label="selectedLabel"
    :selected="selected"
    :visible="visible"
    @clear="onClear"
    @remove-tag="onRemoveTag"
    @focus="onFocus"
    @blur="onBlur"
    @update:visible="updateVisible"
  >
    <div v-if="filterable" class="ele-tree-select-search">
      <el-input
        clearable
        size="mini"
        placeholder="搜索"
        v-model="keywords"
        prefix-icon="el-icon-search"
        @input="filterNode"
      />
    </div>
    <el-scrollbar
      wrap-class="ele-tree-select-dropdown-wrap"
      view-class="ele-tree-select-dropdown-view"
    >
      <el-tree
        ref="tree"
        :props="treeProps"
        :data="data"
        :node-key="valueKey"
        :show-checkbox="multiple"
        :highlight-current="!multiple"
        :expand-on-click-node="expandOnClickNode"
        :check-on-click-node="!expandOnClickNode"
        :filter-node-method="filterNodeMethod"
        :empty-text="noDataText"
        :render-after-expand="renderAfterExpand"
        :load="loadNode"
        :default-expand-all="defaultExpandAll"
        :auto-expand-parent="autoExpandParent"
        :default-expanded-keys="defaultExpandedKeys"
        :check-strictly="checkStrictly"
        :accordion="accordion"
        :indent="indent"
        :icon-class="iconClass"
        :lazy="lazy"
        @node-click="onNodeClick"
        @check="onCheckClick"
      >
        <template slot-scope="{ node, data }">
          <span class="el-tree-node__label ele-text-primary">
            <slot :node="node" :data="data">{{ data[labelKey] }}</slot>
          </span>
        </template>
      </el-tree>
    </el-scrollbar>
  </ele-basic-select>
</template>

<script>
  import EleBasicSelect from '../../ele-basic-select';

  export default {
    name: 'EleTreeSelect',
    components: { EleBasicSelect },
    emits: [
      'input',
      'change',
      'focus',
      'blur',
      'clear',
      'remove-tag',
      'visible-change',
      'search'
    ],
    props: {
      // 绑定值
      value: {
        type: [Number, String, Array],
        required: true
      },
      // 是否多选
      multiple: Boolean,
      // 是否禁用
      disabled: Boolean,
      // 尺寸
      size: String,
      // 是否可以清空选项
      clearable: Boolean,
      // 多选时是否将选中值按文字的形式展示
      collapseTags: Boolean,
      // 占位符
      placeholder: String,
      // 是否可搜索
      filterable: Boolean,
      // 自定义搜索方法
      filterMethod: Function,
      // 是否为远程搜索
      remote: Boolean,
      // 选项为空时显示的文字
      noDataText: String,
      // 对于不可搜索的 Select，是否在输入框获得焦点后自动弹出选项菜单
      automaticDropdown: Boolean,
      // 下拉框的类名
      popperClass: String,
      // popper.js 的参数
      popperOptions: Object,
      // input 的 id 属性
      id: String,
      // input 的 name 属性
      name: String,
      // input 的 autocomplete 属性
      autocomplete: {
        type: String,
        default: 'off'
      },
      // value的键名
      valueKey: {
        type: String,
        default: 'value'
      },
      // label的键名
      labelKey: {
        type: String,
        default: 'label'
      },
      // children的键名
      childrenKey: {
        type: String,
        default: 'children'
      },
      // disabled的键名，指定节点选择框是否禁用
      disabledKey: {
        type: String,
        default: 'disabled'
      },
      // isLeaf的键名，指定节点是否为叶子节点，仅在指定了 lazy 属性的情况下生效
      isLeafKey: {
        type: String,
        default: 'isLeaf'
      },
      // 树形数据
      data: Array,
      // 是否在第一次展开某个树节点后才渲染其子节点
      renderAfterExpand: {
        type: Boolean,
        default: true
      },
      // 加载子树数据的方法，仅当 lazy 属性为true 时生效
      load: Function,
      // 是否默认展开所有节点
      defaultExpandAll: Boolean,
      // 是否在点击节点的时候展开或者收缩节点
      expandOnClickNode: {
        type: Boolean,
        default: false
      },
      // 展开子节点的时候是否自动展开父节点
      autoExpandParent: {
        type: Boolean,
        default: true
      },
      // 默认展开的节点的 key 的数组
      defaultExpandedKeys: Array,
      // 在显示复选框的情况下，是否严格的遵循父子不互相关联的做法
      checkStrictly: Boolean,
      // 是否每次只打开一个同级树节点展开
      accordion: Boolean,
      // 相邻级节点间的水平缩进，单位为像素
      indent: {
        type: Number,
        default: 16
      },
      // 自定义树节点的图标
      iconClass: String,
      // 是否懒加载子节点，需与 load 方法结合使用
      lazy: Boolean
    },
    data() {
      return {
        // 单选选中展示的文本
        selectedLabel: '',
        // 选中的数据
        selected: [],
        // popover是否显示
        visible: false,
        // 搜索关键字
        keywords: '',
        // 树形数据
        treeData: this.data ? this.data : [],
        // 是否是加载状态
        loading: false
      };
    },
    computed: {
      // 树配置项
      treeProps() {
        return {
          label: this.labelKey,
          children: this.childrenKey,
          disabled: this.disabledKey,
          isLeaf: this.isLeafKey
        };
      }
    },
    methods: {
      // 更新visible
      updateVisible(value) {
        this.visible = value;
        this.$emit('visible-change', value);
      },
      // 获取焦点
      onFocus() {
        this.$emit('focus');
      },
      // 失去焦点
      onBlur() {
        this.$emit('blur');
      },
      // 清空
      onClear() {
        this.$emit('input', this.multiple ? [] : '');
        this.$emit('change', this.multiple ? [] : null);
        this.$emit('clear');
        this.visible = false;
        this.$emit('visible-change', false);
      },
      // 删除tag
      onRemoveTag(item) {
        this.$refs?.tree?.setChecked(item.value, false, true);
        this.$emit('input', this.$refs?.tree?.getCheckedKeys() ?? []);
        this.$emit('change', this.$refs?.tree?.getCheckedNodes() ?? []);
        this.$emit(
          'remove-tag',
          this.getNodeByValue(item.value, this.treeData)
        );
      },
      // 树节点点击事件
      onNodeClick(item) {
        const disabled = item[this.disabledKey];
        if (!this.multiple) {
          const sel =
            !this.expandOnClickNode || !item[this.childrenKey]?.length;
          if (sel && !disabled) {
            this.$emit('input', item[this.valueKey]);
            this.$emit('change', item);
            this.visible = false;
            this.$emit('visible-change', false);
          } else if (typeof this.value === 'undefined' || this.value === '') {
            this.$refs?.tree?.setCurrentKey(null);
          } else {
            this.$refs?.tree?.setCurrentKey(this.value);
          }
          return;
        }
        // 多选模式
        const sel = this.expandOnClickNode && !item[this.childrenKey]?.length;
        if (sel && !disabled) {
          const keys = this.$refs?.tree?.getCheckedKeys() ?? [];
          const key = item[this.valueKey];
          this.$refs?.tree?.setChecked(key, !keys.includes(key), true);
          this.$emit('input', this.$refs?.tree?.getCheckedKeys() ?? []);
          this.$emit('change', this.$refs?.tree?.getCheckedNodes() ?? []);
        }
      },
      // 复选框点击事件
      onCheckClick() {
        if (this.multiple) {
          this.$emit('input', this.$refs?.tree?.getCheckedKeys() ?? []);
          this.$emit('change', this.$refs?.tree?.getCheckedNodes() ?? []);
        }
      },
      // 搜索的方法
      filterNodeMethod(value, data) {
        if (this.filterMethod) {
          return this.filterMethod(value, data);
        }
        if (!value) {
          return true;
        }
        return data[this.labelKey].includes(value);
      },
      // 搜索
      filterNode() {
        if (this.remote) {
          this.$emit('search', this.keywords);
        } else {
          this.$refs?.tree?.filter(this.keywords);
          this.$nextTick(() => {
            this.$refs?.select?.updatePopper();
          });
        }
      },
      // value改变事件
      onValueChange(value) {
        // 单选模式
        if (!this.multiple) {
          const isEmpty = this.isEmptyValue(value);
          if (isEmpty) {
            this.selectedLabel = '';
          } else {
            const node = this.getNodeByValue(value, this.treeData);
            this.selectedLabel = node ? node[this.labelKey] : value;
          }
          // 更新树形选中
          this.$refs?.tree?.setCurrentKey(isEmpty ? null : value);
          return;
        }
        // 多选模式
        const checked = [];
        const eachData = (data) => {
          data?.forEach((d) => {
            if (value.includes(d[this.valueKey])) {
              checked.push({
                label: d[this.labelKey],
                value: d[this.valueKey]
              });
              if (this.checkStrictly) {
                eachData(d[this.childrenKey]);
              }
            } else {
              eachData(d[this.childrenKey]);
            }
          });
        };
        eachData(this.treeData);
        this.selected = checked;
        this.$refs?.select?.resetInputHeight();
        // 更新树形选中
        this.$refs?.tree?.setCheckedKeys(value);
      },
      // 单选判断是否是空值
      isEmptyValue(value) {
        return typeof value === 'undefined' || value === null || value === '';
      },
      // 获取value对应数据
      getNodeByValue(value, data) {
        let node;
        if (data?.length) {
          for (let i = 0; i < data.length; i++) {
            if (value === data[i][this.valueKey]) {
              node = data[i];
              break;
            } else {
              node = this.getNodeByValue(value, data[i][this.childrenKey]);
              if (typeof node !== 'undefined') {
                break;
              }
            }
          }
        }
        return node;
      },
      // 懒加载方法
      loadNode(node, resolve) {
        if (!node.data) {
          this.loading = true;
        }
        if (this.load) {
          this.load(node, (data) => {
            if (!node.data) {
              this.treeData = [].concat(data);
              if (this.multiple) {
                if (Array.isArray(this.value) && this.value.length) {
                  this.onValueChange(this.value);
                }
              } else if (this.isEmptyValue(this.value)) {
                this.onValueChange(this.value);
              }
            } else {
              const value = node.data[this.valueKey];
              const item = this.getNodeByValue(value, this.treeData);
              item[this.childrenKey] = [].concat(data);
            }
            resolve(data);
            this.loading = false;
          });
        }
      },
      // 返回目前半选中的节点所组成的数组
      getHalfCheckedNodes() {
        return this.$refs?.tree?.getHalfCheckedNodes();
      },
      // 返回目前半选中的节点的 key 所组成的数组
      getHalfCheckedKeys() {
        return this.$refs?.tree?.getHalfCheckedKeys();
      }
    },
    mounted() {
      if (this.multiple) {
        if (this.value?.length) {
          this.onValueChange(this.value);
        }
      } else if (!this.isEmptyValue(this.value)) {
        this.onValueChange(this.value);
      }
    },
    watch: {
      value(value) {
        this.onValueChange(value);
      },
      checkStrictly() {
        this.onValueChange(this.value);
      },
      data(data) {
        this.treeData = data;
      }
    }
  };
</script>

<style lang="scss">
  .ele-tree-select-dropdown.el-popover {
    padding: 0;

    & > .popper__arrow {
      left: 35px !important;
    }
  }

  .ele-tree-select-dropdown-wrap {
    max-height: 274px;
  }

  .ele-tree-select-dropdown-view {
    padding: 6px 0;

    .el-tree-node[aria-disabled='true'] > .el-tree-node__content {
      cursor: not-allowed;
      background: transparent !important;

      & > .el-tree-node__label {
        opacity: 0.5;
        cursor: not-allowed;
      }
    }

    .el-tree {
      &:not(.el-tree--highlight-current) {
        .el-tree-node > .el-tree-node__content {
          & > .el-tree-node__label {
            color: inherit;
          }
        }
      }

      &.el-tree--highlight-current {
        .el-tree-node:not(.is-current) > .el-tree-node__content {
          & > .el-tree-node__label {
            color: inherit;
          }
        }
      }
    }
  }

  .ele-tree-select-search {
    padding: 6px 10px;
    border-bottom: 1px solid hsla(0, 0%, 60%, 0.15);
  }
</style>
